Im Mittelpunkt dieses Beispiels steht die Gewinnverteilung einer Aktiengesellschaft (AG) nach Artikel 671 des schweizerischen Obligationenrechts (OR). Es soll nicht nur die Themen Objekte und Benutzerschnittstelle vertiefen, sondern auch die phasenweise Entwicklung eines komplexeren Programms veranschaulichen. Das Thema ist wie folgt gegliedert:
Der Ablauf einer doppelten Buchhaltung besteht aus drei Phasen:
Zu Beginn der Buchungsperiode wird die Bilanz (Vermögensrechnung) eröffnet.
Während der Buchungsperiode wird der Geschäftsverkehr doppelt verbucht. Jeder Geschäftsfall wir durch einen oder mehrere Buchungssätze festgehalten. Doppelt verbuchen heisst den Sollspalten eines Buchungssatzes gleich viel belasten wie seinen Habenspalten gutgeschrieben wird.
Am Ende der Buchungsperiode werden die Vermögens- und Schuldkonten in der Bilanz zusammengefasst. Die Aufwand- und Erfolgskonten werden in der Erfolgsrechnung (Gewinn- und Verlustrechnung) abgeschlossen.
Zu den Abschlussarbeiten der Erfolgsrechnung gehört auch die Verbuchung der Gewinnverteilung. Wir veranschaulichen an ihrer Implementation ausgewählte Begriffe der Programmentwicklung unter VBA und Excel. Die Buchhaltung einer Aktiengesellschaft (AG) enthält die folgenden Erfolgsverteilungskonten:
Das Konto Aktienkapital (AK) enthält die Eigentümeranteile
Die Erfolgsrechnung (ER) ermittelt den Gewinn oder Verlust. Dieser wird am Ende der Buchhaltungsperiode auf das Konto ”Gewinn“verteilung übertragen
Der Reservefonds (Res) bewahrt den nicht ausgeschütteten Gewinn auf
Dividende (Div) enthält den Gewinnanteil der Aktionäre
Tantième (Tant) enthält den Gewinnanteil der Verwaltungsräte
Das Konto Gewinnverteilung (GV) verteilt den Erfolg (Gewinn oder Verlust). Der Buchungssatz für die Verteilung der Dividende heisst zum Beispiel Gewinnverteilung an Dividende: Das heisst, die Dividende wird von der Sollspalte des Kontos Gewinnverteilung abgezogen und in der Habenspalte des Passivkontos Dividende als Schuld verbucht. Jede Erfolgsverbuchung ändert so den Saldo des Kontos Gewinnverteilung.
Bei der Präsentation des Beispiels folgen wir den Entwicklungsphasen Spezifikation, Entwurf und Implementation.
Der folgende Bildschirmausschnitt beschreibt den Zweck der Arbeitsmappe Gewinnverteilung.xls. Der Entwickler trägt sie zu Beginn des Projekts unter dem Excel-Menüpunkt »Datei/Eigenschaften ein:
Die folgende Feinspezifikation geht nach dem EVA-Prinzip des Themas Modularisierung vor und wendet die gesetzlichen Vorschriften von OR 671 und die kaufmännische Praxis auf eine konkrete Gewinnverteilungsaufgabe an:
Weil das Programm - anders als ein reales Buchhaltungspaket - den Verkehr der vergangenen Buchungsperiode noch nicht aufgezeichnet hat, muss es den zusammengefassten Verkehr vom Benutzer einlesen, zum Beispiel ...
| Beteiligte Konten |
Beispielsaldi |
|
Aufwand (Unternehmungsaufwand) |
150000.- |
|
Ertrag (Unternehmungsertrag) |
180000.- |
|
Aktienkapital (Grundkapital) |
100000.- |
|
Reserve (Reservefonds) |
5000.- |
|
Tantième in % |
10% |
Alle Buchungssätze der Gewinnverteilung sollen im Format <Sollkonto> an <Habenkonto>, <Betrag> auf dem Tabellenblatt ausgegeben werden:
OR 671 regelt die Erfolgsverteilung einer Aktiengesellschaft. Die geklammerten Ziffern des folgenden Gesetzestexts verweisen auf den entwurfssprachlichen Algorithmus. Reservezuweisungen sind unterstrichen:
"Aus dem Reingewinn ist jährlich ein Betrag von einem Zwanzigstel einem allgemeinen Reservefonds zuzuweisen (1), bis dieser Fonds die Höhe von einem Fünftel des einbezahlten Grundkapitals erreicht hat (2).
Diesem Reservefonds sind, auch nachdem er die gesetzliche Höhe erreicht hat, zuzuweisen: ...
ein Zehntel derjenigen Beträge (3), die aus dem Reingewinn nach der ordentlichen Speisung des Reservefonds (1) und nach Bezahlung einer Dividende von fünf vom Hundert an Aktionäre (4) und sonstige Gewinnbeteiligte (5) verteilt werden."
Die folgende Regelung ergänzt OR 671. Sie ist nicht bindend, in der Praxis aber weit verbreitet:
Nach der ordentlichen Reservezuweisung (s. 1) erhält der Verwaltungsrat eine Tantième in % des Gewinns (5). Nach der zweiten Speisung der Reserve (s. 3) sind vom Gewinnrest soviele ganze Prozent vom einbezahlten Grundkapital wie möglich als Zusatzdividende auszuschütten (6).
Eine verbale Spezifikation ist selten eindeutig. Die Interpretation der oben beschriebenen Erfolgsverteilung ist vor allem aus den folgenden Gründen schwierig:
Der Reingewinn (Nettogewinn oder kurz Gewinn) ist die Differenz von Ertrag und Aufwand
Der Reservefonds enthält jene Gewinnanteile, die nicht als Dividende an Aktionäre oder als Tantième an die Verwaltungsräte verteilt worden sind
Berechnungsbasis der Dividende ist das Grundkapital (Aktienkapital)
Eine erste Zuweisung an die Aktionäre heisst Grunddividende, solange sie einen Fünftel des Aktienkapitals nicht überschreitet. Was darüber hinaus geht, heisst Zusatzdividende (Superdividende). Im Gegensatz zur Grunddividende zieht die Zusatzdividende eine Reservezuweisung nach sich.
Die Zusammenhänge zwischen den Absätzen von OR 671 sind nicht offensichtlich. Gemäss Auslegungspraxis beziehen sich die blauen Textstellen des zweiten Absatzes auf den blauen Teil des ersten Absatzes.
"Aus dem Reingewinn ist jährlich ein Betrag von einem Zwanzigstel einem allgemeinen Reservefonds zuzuweisen, bis dieser Fonds die Höhe von einem Fünftel des einbezahlten Grundkapitals erreicht hat.
Diesem Reservefonds sind, auch
nachdem er die gesetzliche Höhe erreicht
hat, zuzuweisen: ...
ein Zehntel derjenigen Beträge, die aus dem Reingewinn
nach der ordentlichen
Speisung des Reservefonds und nach Bezahlung einer Dividende von fünf
vom Hundert an Aktionäre und sonstige Gewinnbeteiligte
verteilt werden."
OR 671 verwendet Begriffe, die nicht auf Anhieb klar sind. Unter sonstige Gewinnbeteiligte versteht der dritte Absatz zum Beispiel die Verwaltungsräte. Auch ein Zehntel derjenigen Beträge, die ... nach Bezahlung einer Dividende von fünf vom Hundert an Aktionäre und sonstige Gewinnbeteiligte verteilt werden ist mehrdeutig. Der Gesetzgeber will damit Folgendes sagen: Nach Verteilung einer Grunddividende von mindestens 5% müssen 10% einer allfälligen Zusatzdividende und 10% einer allfälligen Tantième den Reserven zugewiesen werden.
Die Praxis schüttet vom Gewinnrest meist soviele ganze Prozent Zusatzdividende vom Aktienkapital wie möglich aus. Eine Zusatzdividende erfordert eine zusätzliche Reservezuweisung von 10% (Reservezuweisung = 0.1*Zusatzdividende). Wir berechnen die ganzzahlige prozentuale Zusatzdividende schrittweise:
Zunächst berechnen wir den Betrag, den wir als Zusatzdividende aus dem Saldo von 'Gewinnverteilung' (Gewinnrest) ausschütten könnten
Dann drücken wir diesen Betrag als genauen Prozentsatz vom Aktienkapital aus.
Diesen genauen Prozentsatz runden wir auf den ganzzahligen ab
Der genaue Zusatzdividendensatz ZusatzdivSatz ergibt sich aus den Gleichungen (1) bis (3) und den algebraischen Vereinfachungen (4) bis (7):
Zusatzdividende = ZusatzdivSatz * 0.01AK (1) ZusatzdivSatz = (GV - ResAufZusatzdiv) / 0.01AK (2) ResAufZusatzdiv = 0.1 * ZusatzdivSatz * 0.01AK (3)
(3) ist die Reservevorschrift von OR 671. (2) berechnet den ZusatzdivSatz, der sich nach Abzug der Reservezuweisung ResAufZusatzdiv noch aus dem Gewinnrest ausschütten lässt. Gleichung (4) vereinfacht (2), indem sie ResAufZusatzdiv durch (3) ersetzt. Nach den Schritten (5) und (6) ergibt (7) schliesslich den ZusatzdivSatz als Quotienten zwischen dem Gewinnrest GV und dem Produkts 0.011AK:
ZusatzdivSatz = (GV - 0.1 * ZusatzdivSatz * 0.01AK) / 0.01 AK (4) ZusatzdivSatz = GV / 0.01 AK - 0.1 * ZusatzdivSatz (5) 1.1 ZusatzdivSatz = GV / 0.01AK (6) ZusatzdivSatz = GV / 0.01AK / 1.1 = GV / 0.011AK (7)
Die VBA-Funktion Int ergibt den ganzzahligen Anteil ihres Arguments. Int(99.8) ergibt zum Beispiel die Integer-Zahl 99. Den ganzzahligen Zusatzdividendensatz berechnen wir deshalb als Int(GV / 0.011AK). Nach (1) lautet die VBA-Formel für die Zusatzdividende deshalb:
Zusatzdividende = Int(GV / 0.011AK) * 0.01AK
Die Arbeitsmappe Gewinnverteilung.xls setzt sich aus den folgenden Objekten zusammen:
Tabellenblatt Dialogblatt
Formular Eingabeformular
Modul Verarbeitung.
Bevor wir die Gewinnverteilung in VBA implementieren, formulieren wir eine erste entwurfssprachliche Annäherung an den Kernalgorithmus. Dabei verwenden wir die folgenden Abkürzungen:
ER Erfolgsrechnung
GV Gewinnverteilung
AK Aktienkapital
T% Tantième in %
kursiv BuchungssätzeFalls Aufwand < Ertrag dann '---- Gewinn Gewinn = Ertrag - Aufwand ER an GV, Gewinn Falls Reserve < AK / 5 dann '(2) GV an Reserve, Gewinn / 20 '(1) GV an Dividende, Aktienkapital / 20 '(4) GV an Tantième, Gewinn * T% / 100 '(5) GV an Reserve, Gewinn * T% / 100 / 10 '(3) ZusatzDivSatz = GV / 0.011AK 'siehe oben (3 c) Zusatzdividende = AK * Int (ZusatzDivSatz) / 100 GV an Dividende, Zusatzdividende '(5,6) GV an Reserve, Zusatzdividende / 10 '(3) Falls GV > 0 dann GV an Reserve, GV sonst '---- Verlust Verlust = Aufwand - Ertrag Falls Verlust < Reserve dann Reserve an ER, Verlust sonst Reserve an ER, Reserve GV an ER, Verlust - ReserveGewinnverteilung gemäss OR 671 und Praxis
Wer diesen Algorithmus mit der Subroutine Buchungssätze()der Arbeitsmappe Gewinnverteilung.xls vergleicht, stösst auf den folgenden Unterschied: Die programmiersprachliche Version prüft für jeden Buchungssatz, ob der laufende Saldo des Gewinnverteilungskontos für die Buchung ausreicht. Um das Verständnis zu erleichtern haben wir diese Prüfung weggelassen und nur nur den algorithmischen Kern skizziert.
Der grösste Teil von Gewinnverteilung.xls widmet sich der Benutzeroberfläche. Das folgende Bild zeigt im Hintergrund die vier Spalten des Tabellenblatts Dialogblatt. Die Ausgabeanweisung Range(Zelle).Value = Wert schreibt jeden vom Programm ermittelten Wert in die passende Zelle von Dialogblatt. Ein Klick auf die Schaltflläche Start ruft die Ereignisprozedur Start_Click() auf, die ihrerseits das Eingabeformular 'Falldaten eingeben' anzeigt.
Auf dem farbigen Hintergrund des Tabellenblatts sehen Sie ...
ein Eingabeformular mit fünf weissen Textfeldern und Defaultwerten
Wie ein Tabellenblatt stellt auch ein benutzerdefiniertes Formular Objekte, Eigenschaften und Methoden bereit. Das obige Eingabeformular 'Falldaten eingeben' enthält für jede Eingabe ein Bezeichnungsfeld und ein Textfeld (weisses Feld rechts vom Bezeichnungsfeld).
Das nächste Bild zeigt den Entwurf des oben gezeigten Eingabeformulars. Der Entwurf verläuft in vier Schritten:
1. Entwicklungsumgebung aufrufen (»Alt/F11)
2. Leeren Formularrahmen zeichnen
3. Steuerlemente in den Formularrahmen setzen (Bezeichnungs- und Textfelder; Schaltflächen)
4. Den Steuerelementen Ereignisprozeduren zuordnen (abbrechen_Click, verbuchen_Click).
Entwurf des Eingabeformulars "Falldaten eingeben"Das fertige Formular lässt sich mit mit dem VBA-Befehl Show anzeigen und mit Hide vorübergehend ausblenden.
Nach dem Entwurf des algorithmischen Kerns und der Benutzeroberfläche skizzieren wir den VBA-Code. Elemente des Objektmodells von Excel sind blau, 'Schaltflächen' stehen zwischen Hochkommas und Ereignisprozeduren sind rot. Anders als der Programmcode kontrolliert die entwurfssprachliche Variante von Buchungssätze() nicht für jeden Buchungssatz, ob der Saldo des Kontos Gewinnverteilung noch ausreicht.
Ereignisprozedur Start_Click() Ausgabebereich des Tabellenblatts Dialogblatt löschen Eingabeformular mit ‘Abbrechen’ und ‘Verbuchen’ anzeigen Falls abgebrochen <> True dann Buchungssätze()Ereignisprozedur abbrechen_Click() Eingabeformular nach Klick auf ‘Abbrechen’ schliessen abgebrochen = TrueEreignisprozedur verbuchen_Click() '--- Leere und nichtnummerische Eingaben verhindern Für jedes Feld von Eingabeformular Falls Feld leer oder nichtnummerisch dann Formular nach Klick auf ‘Verbuchen’ nicht schliessen Fehlermeldung zeigen Kontrolle an den Benutzer zurückgebenSubroutine Buchungssätze() Eingabeformular lesen (Aufwand, Ertrag, Aktienkapital, Reserve, Tantième in %) '-- Buchungssätze gemäss OR 671 und Praxis auf Dialogblatt schreiben Falls Gewinn dann BS "ER", "GV", Gewinn 'Reingewinn Falls Reserve < AK dann BS "GV", "Reserve", Gewinn / 20 BS "GV", "Dividende", Aktienkapital / 20 BS "GV", "Reserve", Gewinn / 20 / 10 BS "GV", "Tantieme", Gewinn * Tantiemensatz / 100 BS "GV", "Reserve", Gewinn * Tantiemensatz / 100 / 10 BS "GV", "Dividende", AK * Int(GV / 0.011AK) / 100 BS "GV", "Reserve", AK * Int(GV / 0.011AK) / 100 / 10 Falls GV < 0 dann "GV", "Reserve", GV sonst ‘Verlust Falls Verlust < Reserve DANN BS "Reserve", "ER", Verlust sonst BS "Reserve", "ER", Reserve BS "GV", "ER", Verlust - ReserveSubroutine BS(Sollkonto, Habenkonto, Buchungsbetrag) Buchungssatz auf Dialogblatt schreiben Konto Gewinnverteilung nachführenModul Verarbeitung (Objekt, Ereignisprozedur, ‘Schaltfläche’)
Wir untersuchen den Programmcode nur ausschnittsweise:
In einem ersten Schritt unterscheiden wir zwischen Variablen, die für das ganze Projekt, für ein Modul oder nur eine Prozedur gelten.
Dann analysieren wir die Ereignisprozeduren der Start-Schaltfläche und des Eingabeformulars.
Schliesslich gewinnen wir einen Überblick über das ganze Programmierprojekt Gewinnverteilung und lernen den den Objektkatalog kennen.
Der folgende Code-Abschnitt deklariert zu Beginn die Variable abgebrochen als öffentlich (Public), damit sie im ganzen Projekt, das heisst sowohl im Modul Dialogblatt als auch im Modul Eingabeformular, verwendet werden kann. Ausgabezeile und GV sind hingegen Private-Variablen, weil sie je nur in einem einzigen Modul vorkommen.
' --- Public für alle Module sichtbar Public abgebrochen As Boolean ' True, falls “Abbrechen” ' --- Private nur für das Verarbeitungsmodul sichtbar Private Ausgabezeile As Integer Private GV As Currency ' Konto Gewinnverteilung
Eine Variable, die nur in jenem Modul oder in jener Prozedur sichtbar ist, in der sie vereinbart worden ist, heisst lokal. Der Programmierer darf zum Beispiel eine private Variable nur in jenem Modul verwenden, in dem er sie eingeführt hat. Wenn er hingegen eine Variable als Public vereinbart, dann darf er sie auch ausserhalb des deklarierenden Moduls, das heisst im ganzen Projekt, ansprechen. Variablen, die auch ausserhalb des deklarierenden Unterprogramms oder Moduls gelten, heissen global.
Einfachprogramme enthalten allerdings die Schlüsselwörter Public und Private nur selten. Am häufigsten ist das Schlüsselwort Dim. Es dient der Vereinbarung einer Variablen innerhalb einer einzigen Prozedur. Man sagt auch, der Gültigkeitsbereich einer Dim-Variablen beschränke sich auf die Prozedur. Private deklariert hingegen Variablen, die für alle Prozeduren eines Moduls gelten; solche Variablen sind also global innerhalb eines Moduls. Public macht sogar eine Vereinbarung für alle Module eines Projekts - zum Beispiel aller Module des Projekts Gewinnverteilung - sichtbar. Solche Variablen sind innerhalb eines Projekts global.
Sub Start_Click()
' --- Dialogblatt und Eingabeformular initialisieren
GV = 0
abgebrochen = False
' Ausgabebereich des Dialogblatts löschen
Dialogblatt.Range("A4:F11").ClearContents
' Eingabeformular die Kontrolle übergeben
Eingabeformular.Show ' laden und anzeigen
' --- Gefülltes Eingabeformular verarbeiten
If abgebrochen = False Then
Buchungssätze
Unload Eingabeformular ' aus Speicher entfernen
End If
End Sub
Die Ereignisprozedur Start_Click() lädt mit dem Befehl Eingabeformular.Show das Eingabeformular in den Internspeicher und zeigt es auf dem Bildschirm. Die Zeile Unload Eingabeformular entfernt das Formular erst, nachdem der Benutzer die Dateneingabe regulär abgeschlossen hat oder die Schaltfläche Abbrechen geklickt hat. Das Programm erkennt am Wert True der Public-Variable abgebrochen, dass der Benutzer die Eingabe abgebrochen hat. Anders als Unload Eingabeformular löscht Eingabeformular.Hide das Eingabeformular nicht aus dem Internspeicher, sondern zeigt es nur bis zum Befehl Eingabeformular.Show nicht mehr an.
Der nächste Programmausschnitt enthält die Ereignisprozeduren der beiden Schaltflächen Abbrechen und Verbuchen. Verbuchen_Click() prüft nur die Gültigkeit der Eingabedaten, die Kernprozedur Buchungssätze wird aus Start_Click() aufgerufen.
'Modul Eingabeformular
'---------------------
Private Sub Abbrechen_Click()
abgebrochen = True
Unload Eingabeformular
End Sub
Private Sub Verbuchen_Click()
'Eingabeformular nach Klick auf "Verbuchen" prüfen
'Aufwand, etc. sind Textfelder des Eingabeformulars
'Aufwand="" bedeutet dasselbe wie Aufwand.Value=""
If Aufwand = "" Or _
Ertrag = "" Or _
Aktienkapital = "" Or _
Reserve = "" Or _
TantièmeInProzent = ""
Then
MsgBox "Keine Leereingaben möglich"
ElseIf Not (IsNumeric(Aufwand) And _
IsNumeric(Ertrag) And _
IsNumeric(Aktienkapital) And _
IsNumeric(Reserve) And _
IsNumeric(TantièmeInProzent))
Then
MsgBox "Nur nummerische Eingaben möglich"
Else
Eingabeformular.Hide 'vorübergehend ausblenden
End If
End Sub
In einem grösseren Projekt wie Gewinnverteilung helfen der Projektexplorer und der Objektkatalog, die Übersicht zu bewahren.